﻿using System;
using System.Collections.Generic;

using System.Text;

namespace RoughJs.Ast
{
    public class SpecialRef : Ref
    {

        private const int SPECIAL_NONE = 0;
        private const int SPECIAL_PROTO = 1;
        private const int SPECIAL_PARENT = 2;

        private Scriptable target;
        private int type;
        private String name;

        private SpecialRef(Scriptable target, int type, String name)
        {
            this.target = target;
            this.type = type;
            this.name = name;
        }

        public static Ref createSpecial(Context cx, Object object1, String name)
        {
            Scriptable target = ScriptRuntime.toObjectOrNull(cx, object1);
            if (target == null)
            {
                throw ScriptRuntime.undefReadError(object1, name);
            }

            int type;
            if (name.Equals("__proto__"))
            {
                type = SPECIAL_PROTO;
            }
            else if (name.Equals("__parent__"))
            {
                type = SPECIAL_PARENT;
            }
            else
            {
                throw new ArgumentException(name);
            }

            if (!cx.hasFeature(Context.FEATURE_PARENT_PROTO_PROPERTIES))
            {
                // Clear special after checking for valid name!
                type = SPECIAL_NONE;
            }

            return new SpecialRef(target, type, name);
        }

        public override object get(Context cx)
        {
            switch (type)
            {
                case SPECIAL_NONE:
                    return ScriptRuntime.getObjectProp(target, name, cx);
                case SPECIAL_PROTO:
                    return target.getPrototype();
                case SPECIAL_PARENT:
                    return target.getParentScope();
                default:
                    throw Kit.codeBug();
            }
        }

        public override object set(Context cx, object value)
        {
            switch (type)
            {
                case SPECIAL_NONE:
                    return ScriptRuntime.setObjectProp(target, name, value, cx);
                case SPECIAL_PROTO:
                case SPECIAL_PARENT:
                    {
                        Scriptable obj = ScriptRuntime.toObjectOrNull(cx, value);
                        if (obj != null)
                        {
                            // Check that obj does not contain on its prototype/scope
                            // chain to prevent cycles
                            Scriptable search = obj;
                            do
                            {
                                if (search == target)
                                {
                                    throw Context.reportRuntimeError1(
                                        "msg.cyclic.value", name);
                                }
                                if (type == SPECIAL_PROTO)
                                {
                                    search = search.getPrototype();
                                }
                                else
                                {
                                    search = search.getParentScope();
                                }
                            } while (search != null);
                        }
                        if (type == SPECIAL_PROTO)
                        {
                            target.setPrototype(obj);
                        }
                        else
                        {
                            target.setParentScope(obj);
                        }
                        return obj;
                    }
                default:
                    throw Kit.codeBug();
            }
        }

        public override bool has(Context cx)
        {
            if (type == SPECIAL_NONE)
            {
                return ScriptRuntime.hasObjectElem(target, name, cx);
            }
            return true;
        }

        public override bool delete(Context cx)
        {
            if (type == SPECIAL_NONE)
            {
                return ScriptRuntime.deleteObjectElem(target, name, cx);
            }
            return false;
        }

    }// end for class
}
